﻿using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace AirLinq.Providers {
	public abstract class ExpressionVisitor {
		protected virtual Expression Visit(Expression exp) {
			if (exp==null){
				return exp;
			}
			switch (exp.NodeType) {
				case ExpressionType.Add:
				case ExpressionType.AddChecked:
				case ExpressionType.And:
				case ExpressionType.AndAlso:
				case ExpressionType.ArrayIndex:
				case ExpressionType.Coalesce:
				case ExpressionType.Divide:
				case ExpressionType.Equal:
				case ExpressionType.ExclusiveOr:
				case ExpressionType.GreaterThan:
				case ExpressionType.GreaterThanOrEqual:
				case ExpressionType.LeftShift:
				case ExpressionType.LessThan:
				case ExpressionType.LessThanOrEqual:
				case ExpressionType.Modulo:
				case ExpressionType.Multiply:
				case ExpressionType.MultiplyChecked:
				case ExpressionType.NotEqual:
				case ExpressionType.Or:
				case ExpressionType.OrElse:
				case ExpressionType.Power:
				case ExpressionType.RightShift:
				case ExpressionType.Subtract:
				case ExpressionType.SubtractChecked:
					return VisitBinary((BinaryExpression)exp);
				case ExpressionType.ArrayLength:
				case ExpressionType.Convert:
				case ExpressionType.ConvertChecked:
				case ExpressionType.Negate:
				case ExpressionType.NegateChecked:
				case ExpressionType.Not:
				case ExpressionType.Quote:
				case ExpressionType.TypeAs:
				case ExpressionType.UnaryPlus:
					return VisitUnary((UnaryExpression)exp);
				case ExpressionType.Call:
					return VisitMethodCall((MethodCallExpression)exp);
				case ExpressionType.Conditional:
					return VisitConditional((ConditionalExpression)exp);
				case ExpressionType.Constant:
					return VisitConstant((ConstantExpression)exp);
				case ExpressionType.Invoke:
					return VisitInvocation((InvocationExpression)exp);
				case ExpressionType.Lambda:
					return VisitLambda((LambdaExpression)exp);
				case ExpressionType.ListInit:
					return VisitListInit((ListInitExpression)exp);
				case ExpressionType.MemberAccess:
					return VisitMemberAccess((MemberExpression)exp);
				case ExpressionType.MemberInit:
					return VisitMemberInit((MemberInitExpression)exp);
				case ExpressionType.New:
					return VisitNew((NewExpression)exp);
				case ExpressionType.NewArrayBounds:
				case ExpressionType.NewArrayInit:
					return VisitNewArray((NewArrayExpression)exp);
				case ExpressionType.Parameter:
					return VisitParameter((ParameterExpression)exp);
				case ExpressionType.TypeIs:
					return VisitTypeIs((TypeBinaryExpression)exp);
			}
			throw new NotImplementedException("expression type not supported");
		}
		protected virtual BinaryExpression VisitBinary(BinaryExpression exp) {
			var left = Visit(exp.Left);
			var right = Visit(exp.Right);
			var conversion = exp.Conversion != null ? VisitLambda(exp.Conversion) : null;
			if (left == exp.Left && right == exp.Right && conversion == exp.Conversion) {
				return exp;
			}
			return Expression.MakeBinary(exp.NodeType, left, right, exp.IsLiftedToNull, exp.Method, conversion);
		}
		protected virtual UnaryExpression VisitUnary(UnaryExpression exp) {
			var operand = Visit(exp.Operand);
			if (operand == exp.Operand) {
				return exp;
			}
			return Expression.MakeUnary(exp.NodeType, operand, exp.Type, exp.Method);
		}
		protected virtual MethodCallExpression VisitMethodCall(MethodCallExpression exp) {
			var instance = Visit(exp.Object);
			var arguments = VisitExpressionList(exp.Arguments);
			if (instance == exp.Object && arguments == exp.Arguments) {
				return exp;
			}
			return Expression.Call(instance, exp.Method, arguments);
		}
		protected virtual ConditionalExpression VisitConditional(ConditionalExpression exp) {
			var test = Visit(exp.Test);
			var ifTrue = Visit(exp.IfTrue);
			var ifFalse = Visit(exp.IfFalse);
			if (test == exp.Test && ifTrue == exp.IfTrue && ifFalse == exp.IfFalse) {
				return exp;
			}
			return Expression.Condition(test, ifTrue, ifFalse);
		}
		protected virtual ConstantExpression VisitConstant(ConstantExpression exp) {
			return exp;
		}
		protected virtual InvocationExpression VisitInvocation(InvocationExpression exp) {
			var arguments = VisitExpressionList(exp.Arguments);
			var expression = Visit(exp.Expression);
			if (arguments == exp.Arguments && (expression == exp.Expression)) {
				return exp;
			}
			return Expression.Invoke(expression, arguments);
		}
		protected virtual LambdaExpression VisitLambda(LambdaExpression exp) {
			var body = Visit(exp.Body);
			var parameters = VisitParameterExpressionList(exp.Parameters);
			if (body == exp.Body && parameters == exp.Parameters) {
				return exp;
			}
			return Expression.Lambda(exp.Type, body, parameters);
		}
		protected virtual ReadOnlyCollection<ParameterExpression> VisitParameterExpressionList(ReadOnlyCollection<ParameterExpression> source) {
			List<ParameterExpression> list = null;
			var count = source.Count;
			for (int i = 0; i < count; i++) {
				var item = VisitParameter(source[i]);
				if (list != null) {
					list.Add(item);
				} else if (item != source[i]) {
					list = new List<ParameterExpression>(count);
					for (var j = 0; j < i; j++) {
						list.Add(source[j]);
					}
					list.Add(item);
				}
			}
			if (list != null) {
				return list.AsReadOnly();
			}
			return source;
		}
		protected virtual ReadOnlyCollection<Expression> VisitExpressionList(ReadOnlyCollection<Expression> source){
			List<Expression> list = null;
			var count=source.Count;
			for (int i = 0; i < count; i++) {
				var item = Visit(source[i]);
				if (list != null) {
					list.Add(item);
				} else if (item != source[i]) {
					list = new List<Expression>(count);
					for (var j = 0; j < i; j++) {
						list.Add(source[j]);
					}
					list.Add(item);
				}
			}
			if (list != null) {
				return list.AsReadOnly();
			}
			return source;
		}
		protected virtual ReadOnlyCollection<ElementInit> VisitElementInitializerList(ReadOnlyCollection<ElementInit> source) {
			List<ElementInit> list = null;
			var count = source.Count;
			for (int i = 0; i < count; i++) {
				var item = VisitElementInitializer(source[i]);
				if (list != null) {
					list.Add(item);
				} else if (item != source[i]) {
					list = new List<ElementInit>(count);
					for (var j = 0; j < i; j++) {
						list.Add(source[j]);
					}
					list.Add(item);
				}
			}
			if (list != null) {
				return list.AsReadOnly();
			}
			return source;
		}
		protected virtual ReadOnlyCollection<MemberBinding> VisitBindingList(ReadOnlyCollection<MemberBinding> source) {
			List<MemberBinding> list = null;
			var count = source.Count;
			for (int i = 0; i < count; i++) {
				var item = VisitBinding(source[i]);
				if (list != null) {
					list.Add(item);
				} else if (item != source[i]) {
					list = new List<MemberBinding>(count);
					for (var j = 0; j < i; j++) {
						list.Add(source[j]);
					}
					list.Add(item);
				}
			}
			if (list != null) {
				return list.AsReadOnly();
			}
			return source;
		}

		protected virtual MemberBinding VisitBinding(MemberBinding exp) {
			switch (exp.BindingType) {
				case MemberBindingType.Assignment:
					return VisitMemberAssignment((MemberAssignment)exp);

				case MemberBindingType.MemberBinding:
					return VisitMemberMemberBinding((MemberMemberBinding)exp);

				case MemberBindingType.ListBinding:
					return VisitMemberListBinding((MemberListBinding)exp);
			}
			throw new InvalidOperationException("unsupported binding type");

		}
		protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding exp) {
			var initializers = VisitElementInitializerList(exp.Initializers);
			if (initializers == exp.Initializers) {
				return exp;			
			}
			return Expression.ListBind(exp.Member, initializers);
		}
		protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding exp) {
			var bindings = VisitBindingList(exp.Bindings);
			if (bindings == exp.Bindings) {
				return exp;
			}
			return Expression.MemberBind(exp.Member, bindings);
		}
		protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment exp) {
			var expression = Visit(exp.Expression);
			if (expression == exp.Expression) {
				return exp;
			}
			return Expression.Bind(exp.Member, expression);
		}

		protected virtual ElementInit VisitElementInitializer(ElementInit elem) {
			var arguments = VisitExpressionList(elem.Arguments);
			if (arguments == elem.Arguments) {
				return elem;
			}
			return Expression.ElementInit(elem.AddMethod, arguments);
		}
		protected virtual ListInitExpression VisitListInit(ListInitExpression exp) {
			var newExpr = VisitNew(exp.NewExpression);
			var initilizers = VisitElementInitializerList(exp.Initializers);
			if (newExpr == exp.NewExpression && initilizers == exp.Initializers) {
				return exp;
			}
			return Expression.ListInit(newExpr, initilizers);
		}

		protected virtual MemberExpression VisitMemberAccess(MemberExpression exp) {
			var expression = Visit(exp.Expression);
			if (expression == exp.Expression) {
				return exp;
			}
			return Expression.MakeMemberAccess(expression, exp.Member);
		}
		protected virtual MemberInitExpression VisitMemberInit(MemberInitExpression exp) {
			var newExpr = VisitNew(exp.NewExpression);
			var bindings = VisitBindingList(exp.Bindings);
			if (newExpr == exp.NewExpression && bindings == exp.Bindings) {
				return exp;
			}
			return Expression.MemberInit(newExpr, bindings);
		}
		protected virtual NewExpression VisitNew(NewExpression exp) {
			var arguments = VisitExpressionList(exp.Arguments);
			if (arguments == exp.Arguments) {
				return exp;
			}
			if (exp.Members != null) {
				return Expression.New(exp.Constructor, arguments, exp.Members);
			}
			return Expression.New(exp.Constructor, arguments);
		}
		protected virtual NewArrayExpression VisitNewArray(NewArrayExpression exp) {
			var expressions = exp.Expressions;
			if (expressions == exp.Expressions) {
				return exp;
			}
			if (exp.NodeType == ExpressionType.NewArrayInit) {
				return Expression.NewArrayInit(exp.Type.GetElementType(), expressions);
			}
			return Expression.NewArrayBounds(exp.Type.GetElementType(), expressions);
		}
		protected virtual ParameterExpression VisitParameter(ParameterExpression exp) {
			return exp;
		}
		protected virtual Expression VisitTypeIs(TypeBinaryExpression exp) {
			var expression=Visit(exp.Expression);
			if (expression == exp.Expression) {
				return exp;
			}
			return Expression.TypeIs(expression, exp.TypeOperand);
		}
	}
}
